package com.hzit.seckill.data;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.shaded.org.checkerframework.checker.units.qual.A;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hzit.common.constant.HzitBusinessConstants;
import com.hzit.common.utils.MyDate;
import com.hzit.seckill.entity.SeckillGoods;
import com.hzit.seckill.service.ISeckillGoodsService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 类名：
 * 作者：WF
 * 功能：
 */
@EnableScheduling
@Component
public class LoadSeckillData {

	@Autowired
	private ISeckillGoodsService seckillGoodsService;
	@Autowired
	private StringRedisTemplate redisTemplate;
	@Autowired
	private RedissonClient redissonClient;

	//1. redis数据与数据库中的数据进行同步
	@Scheduled(cron = "0/30 * * * * * ")
	public void loadData() {
		//1.1 为当前获取数据加锁，保证幂等性
		RLock lock = redissonClient.getLock(HzitBusinessConstants.SECKILL_LOCK_PREFIX);
		lock.lock(5, TimeUnit.SECONDS);

		try {
			//1.1 得到与当前时间接近的五个时间段
			List<Date> dateMenus = MyDate.getDateMenus();
			//1.2 遍历五个时间段
			for (Date dateMenu : dateMenus) {
				//1.2.1 构造查询条件
				QueryWrapper<SeckillGoods> queryWrapper = new QueryWrapper<>();
				queryWrapper.gt("stock_count", 0)            // 库存量大于0
						.eq("status", 1)                     // 状态值为1，代表审核通过
						.ge("start_time", dateMenu)              // 数据库存放的开始时间大于当前时间段的起始时间
						.le("end_time", MyDate.addDateHour(dateMenu, 2)); // 数据库存放的结束时间小于当前时间段加两小时

				//1.2.2 得到redis的hashOperations对象
				HashOperations<String, String, String> hashOperations = redisTemplate.opsForHash();
				//1.2.3 得到redis所有的key的集合
				Set<String> keys = hashOperations.keys(HzitBusinessConstants.SECKILL_GOODS_PREFIX + MyDate.getDateStr(dateMenu));

				//1.2.4 如果数据库中商品id不在redis中的小key范围中，就证明redis中没有刚才添加的数据
				queryWrapper.notIn(CollectionUtil.isNotEmpty(keys),"id", keys);

				//1.2.5 开始条件查询
				List<SeckillGoods> list = seckillGoodsService.list(queryWrapper);
				//1.2.6 因为有可以要修改商品库存数量，所以，一般一个个放到redis中
				for (SeckillGoods seckillGoods : list) {
					//① 放商品到redis中
					hashOperations.put("SeckillGoods_" + MyDate.getDateStr(dateMenu), seckillGoods.getId() + "", JSON.toJSONString(seckillGoods));
					//② 为了避免商品超卖，我们可以定义一个数组，数组的长度就是商品的库存量，数组的元素内容，可以是任意值，这里我们使用商品id，每次下单时我们
					// 我们从这个数组中弹出一件商品，当弹出的商品为null时，代表商品己卖完！
					// 第一阶段：准备数组的数据
					String[] ids = new String[seckillGoods.getStockCount()];
					for (int i = 0; i < ids.length; i++) {
						ids[i] = seckillGoods.getId() + "";
					}
					// 第二阶段：将数组放到redis中,每件商品对应于一个数组！
					redisTemplate.boundListOps(HzitBusinessConstants.SECKILL_OVERSELL_PREFIX + seckillGoods.getId()).leftPushAll(ids);

					//第三阶段：为了达到精准控制购买商品，我们可以在指定key中放商品的库存数，当下单时我们可以自增-1，然后，重新设置回库存
					redisTemplate.boundHashOps(HzitBusinessConstants.SECKILL_PRECISE).put(seckillGoods.getId() + "",seckillGoods.getStockCount());
				}
				//1.2.7 可对放入的商品添加到期时间
				//① 计算剩下时间的毫秒值
				long ttl = MyDate.addDateHour(dateMenu,2).getTime()-new Date().getTime();
				//② 设置商品的到期时间
				hashOperations.getOperations().expire(HzitBusinessConstants.SECKILL_GOODS_PREFIX + MyDate.getDateStr(dateMenu),
						ttl,TimeUnit.MILLISECONDS);


			}
		} finally {
			// 最后，释放锁
			lock.unlock();
		}
	}
}
